/*
  Copyright (c) 2010 Les Newell. All rights reserved

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef MODULE_H_INCLUDED
#define MODULE_H_INCLUDED

#include "common.h"
#include "array.h"

class Socket;
class Socket8;
class Socket16;
class SocketF;
class PlugF;


#define MAX_MODULES 20

///A reference to a socket
struct SOCKETRECORD
{
    Socket * socket;
    const char * name;
};

///Array of sockets
class SocketArray : public GrowArray<SOCKETRECORD>
{
public:
    ~SocketArray();
    void Add(const char * name, Socket * socket);
    Socket * Get(const char * name, const char * type = NULL);
    bool Replace(Socket* oldSocket, Socket * newSocket);
};


///\def DEFINE_MODULE(Name,Parent)
///Define a module.
/** Name is the class name, Parent is the parent class.
For exmaple:
\code
DEFINE_MODULE(FooClass,Module)
  FooClass(){only add sockets and the bare minimum of initialization here}
  Init(){Initialize here}
  Loop{This is called on a regular basis}
}
\endcode
Note there is no opening curly bracket but there is a closing curly bracket
*/

#define DEFINE_MODULE(Name,Parent)\
  class Name : public Parent {\
  public:\
    virtual const char * GetName(){return(#Name);}\
    virtual void PrintName(HardwareSerial& port){\
      port.print(#Name);\
      port.print(".");\
      Parent::PrintName(port);}

/// The base class for all modules

class Module
{
public:

///Constructor
/** Only expose variables and parameters in your constructor. Do your initialisation
    in Init()*/
    Module();

///perform your initialization here
/** This includes connecting plugs and general initialization*/
    virtual void Init(){}

///Reference
/**Called when we are stationary and it is safe to re-reference sensors
and clear PID accumulators etc.
If at all possible, return fairly quickly from this, just in case we
are in the air and reference has been called by mistake (e.g it may
have been connected to a transmitter switch that was operated at the
wrong moment.
*/
    virtual void Reference(){};

///A parameter has changed
/**Called when a parameter has been changed (read from EEPROM or via the comms link)

param points to the parameter that has changed.*/
    virtual void ParamChanged(Socket * param){};

///The module's main loop
/**This function is called at the rate specified by SetInterval()

Do most of your work here. Try to keep this code fast*/
    virtual void Loop(const unsigned long& interval){};


///The rest of this stuff is used by the module manager and diagnostics.

///Get the module class name.
/**Automatically created by DEFINE_MODULE()*/
    virtual const char * GetName()
    {
        return("Module");
    }

///Print the module class name.
/**Automatically created by DEFINE_MODULE()*/
    virtual void PrintName(HardwareSerial& port)
    {
        port.print("Module");
    };

///Check if Loop() needs to be called
/**returns true if Loop() was called*/
    bool Poll(const long& time);

///Find a socket of the given name
/**Returns a pointer if this module has the socket. Otherwise
returns false*/
    Socket * GetSocket(const char * name, const char * type = NULL)
    {
        return(m_sockets.Get(name,type));
    }

///Replace an existing socket with a new one
/**Returns true if the socket was found and replaced*/
    bool ReplaceSocket(Socket* oldSocket, Socket * newSocket)
    {
        return(m_sockets.Replace(oldSocket, newSocket));
    }


///Get this module's sockets
    SocketArray& GetSockets()
    {
        return(m_sockets);
    }

///Return true is this module is running it's Loop() function
    inline bool IsRunning(){return(m_running);}

///Get this module's priority
    inline int8_t GetPriority(){return(m_priority);}

///Get the maximum execution time in us
    inline long GetMaxTime(){long t = m_cpuTime; m_cpuTime = 0; return(t);}

///Get the interval in ms
/**The interval defines how often Loop() is run*/
    int GetInterval(){return (m_interval / 1000);}

///Check and possibly update the maximum execution time
    inline void CheckTime(long time)
    {
        if(time > m_cpuTime)
        {
            m_cpuTime = time;
        }

    }

protected:


///Set how often you want Loop() to be called (in ms).
/**Must be greather than 0*/
    void SetInterval(unsigned int time);

///Set our priority.
/**0 is lowest, max 100

Priority -1 means never execute

Try to avoid having too many different priority levels.
Increasing the number of priority levels increases the number of threads
and memory usage. The theoretical maximum number of threads used is
the number of different levels + 1. In practice it is generally lower.
*/
    void SetPriority(const int8_t priority)
    {
        m_priority = priority;
        if(m_priority > 100)
        {
            m_priority = 100;
        }
    }

///Add a socket/parameter to the sockets table
/** NOTE: name must be static. It MUST NOT be created on the stack.*/
    void AddSocket(const char * name, Socket * socket)
    {
        m_sockets.Add(name,socket);
    }

///Add an array of sockets
/**names and sockets must point to
arrays of strings and sockets respectively. n defines how many sockets.*/
    void AddSocketArray(const char * names[], Socket8 sockets[], int n);
///Add an array of sockets
/**names and sockets must point to
arrays of strings and sockets respectively. n defines how many sockets.*/
    void AddSocketArray(const char * names[], Socket16 sockets[], int n);
///Add an array of sockets
/**names and sockets must point to
arrays of strings and sockets respectively .n defines how many sockets.*/
    void AddSocketArray(const char * names[], SocketF sockets[], int n);

///Connect an array of plugs
/**names and plugs must point to
arrays of strings and sockets respectively. n defines how many plugs.*/
    void ConnectPlugArray(const char * names[], PlugF plugs[], int n);

///Calculate the time since we were last called in seconds
    inline float GetTime(const unsigned long& interval)
    {
        float result = interval;
        result /= 1000000;
        return (result);
    }

private:
    SocketArray m_sockets;
    long m_interval;
    long m_lastTime;
    volatile byte m_running;
    int8_t m_priority;
    long m_cpuTime;
};


#define MAX_THREADS 6

#define PRIORITY_NEVER -1

///The module manager
/**This takes care of initializing and executing modules
at the correct times.*/

class ModuleManager
{
public:
    ModuleManager();

///Initialize
/**Call this to initialize all modules
For example with an Arduino you would call this from your Arduino setup() function*/
    void Init();


///Get the module with the given name (case insensitive)
/**If the module does not exist, it returns NULL*/
    Module * GetModule(const char * name);

///Get a socket with the given name and (optional) data type.
/**The name is case insensitive.

If the socket does not exist, returns NULL*/
    Socket * GetSocket(const char * name, const char * type = NULL, int * moduleNumber = NULL);

///Replace an existing socket with a new one
/**Returns true if the socket was found and replaced*/
    bool ReplaceSocket(Socket* oldSocket, Socket * newSocket);


///Set a socket to a value
/** The socket given by name is set to the contents of string

name is case insensitive. for example:
\code ParseParameter("mysocket","123.45");\endcode*/
    bool ParseParameter(char * name, char * string);

///Write all of the parameters to non volatile memory
/**Note for porting: You need to provide an EEPROM class that works in
the same way as the Arduino EEPROM class.*/
    bool WriteParameters();

///Reference all sensors.
/**Only call this when you are safely on the ground*/
    void Reference();

///Add a module
/**The moduale is added to the internal module list.*/
    void Add(Module * module){m_modules.Add(module);}

///Get the number of modules
    inline int GetCount(){return(m_modules.GetCount());}

///Get a module by index
    inline Module * GetModule(int mod){return (m_modules[mod]);}

///Print version data
    void PrintVersion(Print& print, const bool full);

///Interrupt handler
/**Note for porting: This needs to be called at 1ms intervals.
Normally it would be called from a re-entrant interrupt handler.
*/
    static void InterruptHandler();

///Get the number of times we had too many threads running
/**Clears the count*/
    int GetOverloadCount(){int m = m_overloadCount; m_overloadCount = 0; return(m); }
///Get the maximum number of threads that have been running
/**Clears the count*/
    int GetMaxThreads(){int m = m_maxThreads; m_maxThreads = 0; return(m); }
///Get the heart beat
/**This increments every time the interrupt handler is called.*/
    int GetHeartbeat(){return (m_heartbeat & 0x7fff);}

private:

    enum READMODE{modeREAD,modeWRITE,modeCHECKSUM};

    /*Read all of the parameters from non volatile memory
    this is automatically called during initialisation*/
    bool ReadParameters();
    bool ReadWriteParameters(const READMODE mode);
    void CreateChecksum();

    int m_checksum;
    static GrowArray<Module *> m_modules;
    static volatile int m_threadCount;
    static int m_maxThreads;
    static int m_overloadCount;
    static int m_heartbeat;
    static int8_t m_threadStack[MAX_THREADS + 1];
    bool m_started;

};

extern ModuleManager g_moduleManager;

#endif //#define MODULE_H_INCLUDED


